/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class DataQueryContext
  {
    #region Static Fields

    internal static readonly DataQueryContext None = new DataQueryContext();

    #endregion

    #region Constructor

    internal DataQueryContext(
      IEnumerable<SortDescription> sortDescriptions,
      Predicate<object> filterPredicate,
      FilterExpressionBase filterExpression,
      long dataSourceSize )
    {
      if( dataSourceSize < 0 )
        throw new ArgumentOutOfRangeException( "dataSourceSize", "dataSourceSize must be greater than or equal to zero." );

      this.FilterPredicate = filterPredicate;
      this.FilterExpression = filterExpression;
      this.DataSourceSize = dataSourceSize;

      IList<SortDescription> sortOrder = ( sortDescriptions != null )
                                           ? sortDescriptions.ToList()
                                           : new List<SortDescription>( 0 );
      this.SortDescriptions = new ReadOnlyCollection<SortDescription>( sortOrder );
    }

    private DataQueryContext()
      : this( Enumerable.Empty<SortDescription>(), null, null, 0 )
    {
      this.IsNoneContext = true;
    }

    #endregion

    #region FilterPredicate Property

    internal Predicate<object> FilterPredicate
    {
      get;
      private set;
    }

    #endregion

    #region FilterExpression Property

    internal FilterExpressionBase FilterExpression
    {
      get;
      private set;
    }

    #endregion

    #region SortDescriptions Property

    internal IList<SortDescription> SortDescriptions
    {
      get;
      private set;
    }

    #endregion

    #region DataSourceSize Property

    internal long DataSourceSize
    {
      get;
      private set;
    }

    #endregion

    #region IsNoneContext Private Property

    private bool IsNoneContext
    {
      get;
      set;
    }

    #endregion

    #region HashCode Private Property

    private int HashCode
    {
      get
      {
        if( !m_hashCode.HasValue )
        {
          int hash = 17;
          int factor = 37;

          hash = ( hash * factor ) + this.IsNoneContext.GetHashCode();
          hash = ( hash * factor ) + this.DataSourceSize.GetHashCode();

          if( this.FilterPredicate != null )
          {
            hash = ( hash * factor ) + this.FilterPredicate.GetHashCode();
          }

          if( this.FilterExpression != null )
          {
            hash = ( hash * factor ) + this.FilterExpression.GetHashCode();
          }

          foreach( SortDescription sort in this.SortDescriptions )
          {
            hash = ( hash * factor ) + sort.GetHashCode();
          }

          m_hashCode = hash;
        }

        return m_hashCode.Value;
      }
    }

    private Nullable<int> m_hashCode; //null

    #endregion

    public override int GetHashCode()
    {
      return this.HashCode;
    }

    public override bool Equals( object obj )
    {
      if( object.ReferenceEquals( this, obj ) )
        return true;

      DataQueryContext context = obj as DataQueryContext;

      return ( context != null )
          && ( this.GetHashCode() == context.GetHashCode() )
          && ( object.Equals( this.IsNoneContext, context.IsNoneContext ) )
          && ( object.Equals( this.DataSourceSize, context.DataSourceSize ) )
          && ( object.Equals( this.FilterPredicate, context.FilterPredicate ) )
          && ( object.Equals( this.FilterExpression, context.FilterExpression ) )
          && ( this.SortDescriptions.SequenceEqual( context.SortDescriptions ) );
    }

    internal bool IsSimilarContext( DataQueryContext context, out bool reversedSortOrder )
    {
      reversedSortOrder = false;

      if( object.ReferenceEquals( this, context ) )
        return true;

      if( ( context == null )
        || ( !object.Equals( this.FilterPredicate, context.FilterPredicate ) )
        || ( !object.Equals( this.FilterExpression, context.FilterExpression ) )
        || ( !object.Equals( this.SortDescriptions.Count, context.SortDescriptions.Count ) ) )
        return false;

      // Check if the sort order is the same or reversed.
      int count = this.SortDescriptions.Count;
      Nullable<bool> reversed = null;

      for( int i = 0; i < count; i++ )
      {
        SortDescription firstSort = this.SortDescriptions[ i ];
        SortDescription secondSort = context.SortDescriptions[ i ];

        // The two contexts aren't sorted on the same fields.
        if( firstSort.PropertyName != secondSort.PropertyName )
          return false;

        bool isReversed = ( firstSort.Direction != secondSort.Direction );

        if( reversed.HasValue )
        {
          // Make sure the two contexts are either sorted in the same order
          // or reverse order.
          if( reversed.Value != isReversed )
            return false;
        }
        else
        {
          reversed = isReversed;
        }
      }

      reversedSortOrder = reversed.GetValueOrDefault( false );

      return true;
    }
  }
}
